package al.work.httplisten.aop;

import al.work.common.util.ClassUtils;
import al.work.httplisten.aop.annotation.*;
import com.sun.net.httpserver.HttpExchange;
import org.omg.CORBA.ObjectHelper;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author JiuZh
 */
public class HttpAspectHandler {
    class MethodInfo {
        public MethodInfo(Object handler, Method method, int order, Integer joinPointIndex, Integer returnIndex, Integer throwIndex) {
            this.handler = handler;
            this.method = method;
            this.order = order;
            this.joinPointIndex = joinPointIndex;
            this.returnIndex = returnIndex;
            this.throwIndex = throwIndex;
        }

        private Object handler;
        private Method method;
        private int order;
        private Integer joinPointIndex;
        private Integer returnIndex;
        private Integer throwIndex;
    }

    public enum AspectState {
        ASPECT_STATE_BEFORE,
        ASPECT_STATE_AFTER,
        ASPECT_STATE_AFTER_RETURING,
        ASPECT_STATE_AFTER_THROWING,
        ASPECT_STATE_TOTAL
    }

    private final Map<Class<? extends Annotation>, List<MethodInfo>>[] handlers = new HashMap[AspectState.ASPECT_STATE_TOTAL.ordinal()];

    public void initialize(String name) throws Exception {
        for (int i = 0; i < handlers.length; ++i) {
            handlers[i] = new HashMap<>();
        }
        Map<Class<?>, Aspect> classes = ClassUtils.getAnnotationClasses(name, Aspect.class);
        for (Map.Entry<Class<?>, Aspect> entry : classes.entrySet()) {
            Class<?> classis = entry.getKey();
            Object handlerT = classis.newInstance();

            Map<Method, Before> beforeMethods = ClassUtils.getAnnotationMethod(classis, Before.class);
            for (Map.Entry<Method, Before> kv : beforeMethods.entrySet()) {
                String value = kv.getValue().value();
                if (!value.startsWith("@annotation(")) {
                    continue;
                }
                addAnnotationHandler(value, kv.getKey(), handlerT, handlers[AspectState.ASPECT_STATE_BEFORE.ordinal()], null, null);
            }

            Map<Method, After> afterMethods = ClassUtils.getAnnotationMethod(classis, After.class);
            for (Map.Entry<Method, After> kv : afterMethods.entrySet()) {
                String value = kv.getValue().value();
                if (!value.startsWith("@annotation(")) {
                    continue;
                }
                addAnnotationHandler(value, kv.getKey(), handlerT, handlers[AspectState.ASPECT_STATE_AFTER.ordinal()], null, null);
            }

            Map<Method, AfterReturning> afterReturningMethods = ClassUtils.getAnnotationMethod(classis, AfterReturning.class);
            for (Map.Entry<Method, AfterReturning> kv : afterReturningMethods.entrySet()) {
                String value = kv.getValue().value();
                if (!value.startsWith("@annotation(")) {
                    continue;
                }
                kv.getValue().returning();
                addAnnotationHandler(value, kv.getKey(), handlerT, handlers[AspectState.ASPECT_STATE_AFTER_RETURING.ordinal()], kv.getValue().returning(), null);
            }

            Map<Method, AfterThrowing> afterThrowingMethods = ClassUtils.getAnnotationMethod(classis, AfterThrowing.class);
            for (Map.Entry<Method, AfterThrowing> kv : afterThrowingMethods.entrySet()) {
                String value = kv.getValue().value();
                if (!value.startsWith("@annotation(")) {
                    continue;
                }
                addAnnotationHandler(value, kv.getKey(), handlerT, handlers[AspectState.ASPECT_STATE_AFTER_THROWING.ordinal()], null, kv.getValue().throwing());
            }
        }
    }

    protected void addAnnotationHandler(String annotationName, Method method, Object handler, Map<Class<? extends Annotation>, List<MethodInfo>> listMap, String retParamName, String throwParamName) {
        annotationName = annotationName.substring(12).trim();
        if (annotationName.endsWith(")")) {
            annotationName = annotationName.substring(0, annotationName.length() -1);
            annotationName = annotationName.trim();
        }
        Class<? extends Annotation> annotationClass = null;
        try {
            annotationClass = (Class<? extends Annotation>)this.getClass().getClassLoader().loadClass(annotationName);
        } catch (Exception ex) {
            annotationClass = null;
        }
        if (null == annotationClass) {
            return;
        }

        int order_vl = 2147483647;
        Order order = method.getAnnotation(Order.class);
        if (null != order) {
            order_vl = order.value();
        }

        Integer retIndex = null, throwIndex = null;
        if ((null != retParamName && !retParamName.isEmpty()) || (null != throwParamName && !throwParamName.isEmpty())) {
            Parameter[] methodParameters = method.getParameters();
            for (int i = 0; i < methodParameters.length; ++i) {
                Parameter parameter = methodParameters[i];
                if (null != retParamName && parameter.getName().equals(retParamName)) {
                    retIndex = i;
                }
                if (null != throwParamName && parameter.getName().equals(throwParamName)) {
                    throwIndex = i;
                }
            }
        }

        MethodInfo info = new MethodInfo(handler, method, order_vl, getParamIndexByType(method, JoinPoint.class), retIndex, throwIndex);

        List<MethodInfo> list = listMap.get(annotationClass);
        if (null == list) {
            list = new ArrayList<>();
            listMap.put(annotationClass, list);
        }

        int index = 0;
        for (; index < list.size(); ++index) {
            if (list.get(index).order < info.order) {
                list.add(index, info);
                break;
            }
        }
        if (list.size() == index) {
            list.add(info);
        }
    }

    public void handle(AspectState state, Object target, Method method, Object[] args, Object extVl) {
        if (null == state || null == method) {
            return;
        }
        for (Map.Entry<Class<? extends Annotation>, List<MethodInfo>> kv : handlers[state.ordinal()].entrySet()) {
            Annotation classis = method.getAnnotation(kv.getKey());
            if (null == classis) {
                continue;
            }
            List<MethodInfo> list = kv.getValue();
            for (int i = 0; i < list.size(); ++i) {
                MethodInfo info = list.get(i);
                try {
                    Object inArgs[] = new Object[info.method.getParameterTypes().length];
                    Integer index = getParamIndexByType(info.method, JoinPoint.class);
                    if (null != info.joinPointIndex) {
                        inArgs[info.joinPointIndex] = new JoinPointImpl(target, method, args);
                    }
                    if (AspectState.ASPECT_STATE_AFTER_RETURING == state && null!= info.returnIndex) {
                        inArgs[info.returnIndex] = extVl;
                    }
                    if (AspectState.ASPECT_STATE_AFTER_THROWING == state && null!= info.throwIndex) {
                        inArgs[info.throwIndex] = extVl;
                    }
                    info.method.invoke(info.handler, inArgs);
                } catch (Exception ex) {
                }
            }
        }
    }

    protected Integer getParamIndexByType(Method method, Class<?> classType) {
        Class<?>[] classiss = method.getParameterTypes();
        for (Integer i = 0; i < classiss.length; ++i) {
            if (classiss[i].equals(classType)) {
                return i;
            }
        }
        return null;
    }
}
